#require "imgs.rb"
require "movement.rb"
require "dataparse.rb"
require "ai.rb"

$players = {}
$players_data = {}
$players_defaults = {}
#The Player class represents the user's character. It acts as an iterface between the MovementBase class and user input from the GameWindow.
#Player also allows for "bootstrapping" and loading itself into memory apon being supplied with the name of the desired character.
class Player < MovementBase
  attr_reader :name, :dispName, :offsety, :drawx, :drawy, :quickAction, :inventory
  attr_accessor :hp, :maxhp
  DefRoot="data/characters/players/players.def"
  SpriteRoot="img/sprites/chars"
  def initialize(name, hp=8, maxhp=8)
    @name=name
    @hp=hp
    @maxhp=maxhp
    @fixeddraw=true
    @drawx, @drawy = 320, 240
    raise NameError.new("'#{classid}' is not a valid Player class") if $players_data[@name]==nil
    @dispName=$players_data[@name][0]
    @tile=Tile.new(0,0,$players_data[@name][1],$players_data[@name][2]).id
    @offsety=$players_data[@name][3]
    linkAnims
    #linkCustomActions
    super()
    @quickActions = {"z" => nil, "x" => nil, "c" => nil}
    setQuickAction("z", $players_defaults[@name][0]) if $players_defaults[@name][0]
    setQuickAction("x", $players_defaults[@name][0]) if $players_defaults[@name][1]
    setQuickAction("c", $players_defaults[@name][0]) if $players_defaults[@name][2]
    @inventory=Inventory.new
  end
  
  def linkAnims
    @anims = {}
    DataFile.new($players[@name]).each(2) { |args|
      if args!=false
        @anims[args[0]]=args[1]
      end
    }
  end
  def linkCustomActions
   # @customActions = []
   # DataFile.new(File.dirname($players[@name])+"/"+File.basename($players[@name],".def")+"custom"+File.extname($players[@name])).each(1) { |args|
   #   if args!=false
   #     @customActions.push(args[1])
   #   end
   # }
  end
  
  def setDefVel
    @defVel=Velocity::Walking
  end
  
  def tile
    return *Tile.getById(@tile)
  end
  
  def setQuickAction(key,action)
    if key=~/z|x|c/
      @quickActions[key]=action
      return true
    end
    return false
  end
  
  def move
 #   if @vel!=Velocity::Stationary
      super
      if @x-320 > 0 and @x+320 < $window.map.width
        @drawx=320
        $window.camera.x=@x-320
      else
        @drawx=@x-$window.camera.x
      end
      if @y+240 < $window.map.height and @y-240 > 0 
        @drawy=240
        $window.camera.y=@y-240
      else
        @drawy=@y-$window.camera.y
      end
 #   end
  end
  def tryTalk()
    if $textbar.sleeping?
      $window.characters.each { |char|
        if Gosu::distance(@x,@y,char.x,char.y) <= 25
          char.talk(@x,@y)
          return true
        end
      }
    end
    return false
  end
  def actionz
    feedAction(@quickActions["z"])
  end
  def actionx
    feedAction(@quickActions["x"])
  end
  def actionc
    feedAction(@quickActions["c"])
  end
  def defAttack
    feedAction(DefAtk)
  end
  def defDefence
    feedAction(DefBlock)
  end
  def hearts
    return [(@hp/2).floor, hp%2] #first value: # of whole hearts, 2nd value: fraction of partial heart
  end
  def draw
    super(@drawx,@drawy)
  end
  
  def Player.loadTraits
    traits=nil
    traitsclass=ObjectScript::Class.new("player")
    traitsclass.addStringProperty("name")
    traitsclass.addStringProperty("dispname")
    traitsclass.addIntProperty("hp")
    traitsclass.addIntProperty("max_hp")
    #traitsclass.addStringProperty("action",false,true)
    #traitsclass.addStringProperty("dispname",,)
    Datafile.new("temp/runtime/vars/playertraits.def").each(1) { |arg|
      if arg!=nil
        traits=File.new("temp/runtime/vars/"+arg)  
        #ObjectScript.new
      end
    }
  end
end

$npcs = {}
$npcs_data = {}
#NPCClass acts as a shared data resource for NPCInstance instances of the same type.
#NPCClass allows for "bootstrapping" and loading itself into memory apon being supplied with the name of the desired character.
class NPCClass
  attr_reader :name, :dispName, :tile, :offsety
  @@instances = {}
  def initialize(charname)
    @name=charname
    raise NameError.new("'#{classid}' is not a valid NPC class") if $npcs_data[@name]==nil
    @tile=Tile.new(0,0,$npcs_data[@name][1],$npcs_data[@name][2]).id
    @dispName=$npcs_data[@name][0]
    @offsety=$npcs_data[@name][3]
    linkAnims
    #linkCustomActions
    @@instances[@name]=self
  end  

  def linkAnims
    @anims = {}
    DataFile.new($npcs[@name]).each(2) { |args|
      if args!=false
        @anims[args[0]]=args[1]
      end
    }
  end
  def linkCustomActions
  #  @customActions = []
  #  DataFile.new($npcs[@name]+".custom").each(1) { |args|
  #    if args!=false
  #      @customActions.push(args[1])
  #    end
  #  }
  end
  
  def getAnim(direction,action)
     if MovementBase::DirectionMap[direction] != nil and MovementBase::ActionMap[action].class !="nil" 
       return *Animation.get(@anims[MovementBase::DirectionMap[direction]+MovementBase::ActionMap[action]])
     else
       $flog.fatal("Movement Base") {"Invalid animation resource pointer."}
       $flog.info("Movement Base") {"Bad Pointer: #{direction},#{action}"}
       return nil
    end
  end

  def NPCClass.getClass(name)
    return *@@instances[name]
  end
end

#The NPCInstance class represents ta signle NPC. It acts as an iterface between the MovementBase class and the AI module.
#NPCInstance instances of the same type use NPCClass as a shared data resource for all non-unique data.  
class NPCInstance < MovementBase
  DefRoot="data/characters/npcs/npcs.def"
  SpriteRoot="img/sprites/chars"
  @@count=0
  @@batchcount=0
  attr_reader :classid,:dispName,:aigroup,:ai,:id
  attr_accessor :hp
  def initialize(classid, dispName="", speechscript=nil)
    @classid=classid
    @aigroup=@@count % 4
    @@count+=1
    @npcclass=NPCClass.getClass(@classid)
    raise NameError.new("'#{classid}' is not a valid NPC class") if @npcclass==nil
    @offsety=@npcclass.offsety
    @speechscript=speechscript
    @dispName=dispName
    @hp=1
    super()
    @id=$window.newCharacter(self)
  end
  def tile
    return *Tile.getById(@npcclass.tile)
  end
  def name
    return @classid
  end
  def getAnim
    if @locked or @locked==nil
      return @npcclass.getAnim(@direction,Action::Standing) 
    end
    
    @action=@lastaction if @stickaction or @pstickaction
    
    if @action.kind_of?(ActionBase) 
      @action.doAction(self)
      anim=@action.animation(MovementBase::DirectionMap[@direction])
      unstickAction if anim.done 
      return anim
    end
    
    return *@npcclass.getAnim(@direction,@action)
  end
  def move
    if @locked
      @direction=@lockdir
      return
    elsif @locked==nil
      @direction=@lockdir
    end
    if @frozen #or @vel==Velocity::Stationary
      return
    end
    #$trace.sput tile
    return autoMove if @rerouteMovement
    setDefVel
  end
  private
  def collide
    $window.characters.each { |char|
      if @npcclass.tile!=char.tile.id and tile.intersect?(char.tile) 
        return true  
      end
    }
    return false
  end
  public
  def die
    $window.characters.delete_at(@id)
  end
  def setDefVel
    @defVel=Velocity::Walking
  end
  def talk(x=nil,y=nil)
    if x!=nil and y!=nil
      if (@y-y).abs > (@x-x).abs
        if @y > y
          @direction=Direction::Backward
        else
          @direction=Direction::Forward
        end
      else
        if @x > x
          @direction=Direction::Left
        else
          @direction=Direction::Right
        end
      end
    end
    lock
    $window.player.lock
    $textbar.loadText(@speechscript, Proc.new{ |args| args[0].unlock; $window.player.unlock }, Array.new.push(self)) if @speechscript
  end
  def runAI
=begin
    if @@batchcount % 80 == @aigroup 
      AIProc(*self)
    end
    if @@batchcount==160
      @@batchcount=0
    else
      @@batchcount+=1
    end
=end
  end
end

#A static NPC instance is an NPC which does not move around the map.
#Using the StaticNPCInstance class over the NPCInstance class gives a *big* preformace boost.
#StaticNPCInstance uses NoMovement instead of MovementBase as its movement/animation API.
class StaticNPCInstance < NoMovement; end

#Each Player has an Inventory composed of Items. The inventory is saved into the PlayerTraits save component.
class Inventory
  attr_reader :weapons, :items
  def initialize(weapons=[],armors=[],items=[])
    @weapons=weapons
    @armors=armors
    @items=items
  end
  def equipWeapon(weapon, slot=0)
    @eqWep[slot]=weapon
    @weapons.delete(weapon)
  end
  def equipArmor(armor, slot=0)
    @eqArmor[slot]=armor
    @armors.delete(armor)
  end
  def equipItem(item, slot=0)
    @eqItems[slot]=item
    @items.delete(item)
  end
  def addWeapon(weapon)
    @weapons.push(weapon)
  end
  def addArmor(armor)
    @armors.push(armor)
  end
  def addItem(item)
    pos=@items.index(item)
    if(pos!=nil)
      @items.push(item)
    else
      @items[pos].stack(1)
    end
  end
  def curWeapon(slot=0)
    return @eqWep[slot]
  end
  def curArmor(slot=0)
    return @eqArmor[slot]
  end
  def curItem(slot=0)
    return @eqItems[slot]
  end
  def armor
    return @armors
  end
  def useItem(item)
    pos=@items.index(item)
    if(pos!=nil)
      @items.delete_at(pos) unless items[pos].use
    else
      pos=@eqItems.index(item)
      if(pos!=nil)
        @eqItems.delete_at(pos) unless items[pos].use
      end
    end
  end
  def clear
    @weapons=[]
    @armors=[]
    @items=[]
    @eqWep=[]
    @eqArmor=[]
    @eqItems=[]
  end
end

class Item
  def name
    return "unnamed"
  end
  def dispName
    return "Unnamed"
  end
  def useScript
    return "null.rb";
  end
  def ==(obj)
    return (obj.kind_of?(Item) and obj.name==name)
  end
  def use
    RubyScript.new(useScript).run
    return true
  end
end

class UnequipableItem < Item
  def use
    super
    return false
  end
end

class StackableItem < Item
  def stack(num)
    @amount+=num
  end
  def use
    super
    @amount-=1
    if @amount==0 then
      return false
    end
  end
end

class SingleUseItem < Item
  def use
    super
    return false
  end
end

class CombatItem < Item
  def action
    return Action.new
  end
  def useScript
  end
  def use
    return true
  end
end

#The PlayerTraits class represents the variables of a player which may be changed during the course of the game and must be saved along with the savegame.
#These variables include:
# * The player's display name
# * The player's current hp
# * The player's maximum hp
# * The player's inventory (Actually, a reference to the inventory data)
# * The player's current quickActions (Player#quickActions)
class PlayerTraits
  attr_accessor :name, :dispName, :hp, :maxhp, :inventory, :quickActions
  def initialize(name="unnamed", dispName="unnamed", hp=0, maxhp=0, inventory="null.rb", quickActions={"z" => nil, "x" => nil, "c" => nil})
    @name=name
    @dispName=dispName
    @hp=hp
    @maxhp=maxhp
    @inventory=inventory
    @quickActions = quickActions
  end
  #Save the traits for the current player.
  def PlayerTraits.saveCurrentPlayer
    File.open("temp/runtime/vars/"+$window.player.name+".yml", "w") { |file|
      pdata = PlayerTraits.new
      pdata.name = $window.player.name; pdata.dispName= $window.player.dispName
      pdata.hp = $window.player.hp; pdata.maxhp = $window.player.maxhp
      pdata.inventory = $window.player.name+"inv.yml"
      pdata.quickActions = $window.player.quickActions
      file.write(pdata.to_yaml)
    }
    File.open($window.player.name+"inv.yml", "w") { |file|
      file.write($window.player.inventory.to_yaml)
    }
  end
  #Load the traits for the current player.
  def PlayerTraits.loadCurrentPlayer
    pdata = YAML::load()
  end
  #Creates an index for a new campaign. The index contains a list of all players of the campaign.
  def PlayerTraits.createIndex
    f = File.new("temp/runtime/vars/playertraits.def", "w")
    $players.each_key { |key|
      file.write(key+".yml\n")
    }
    f.close
  end
end